﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Xml;
using System.IO;
using log4net;

namespace Dazzle {
    [ComVisible(true), Guid("BF9DC749-0A32-4C39-AF89-68DB8BA3C5DD")]
    public class GraphBox : IDataObject,    // can be rendered
                       IOleObject,     // can be shown within a container application
                       IPersistStorage // can be saved when _embedded_
    {
        public static Guid CLSID { get { return Win32.GetGuidAttribute(typeof(GraphBox)); } }

        private ILog logger = LogManager.GetLogger(typeof(GraphBox));

        public const string USER_TYPE = "Gildata Word Graph BOX";
        public const string USER_TYPE_LONG = "Gildata Word Graph with matlab";

        private string _name = "GraphBox1";
        private string _graphType = "Line";

        // Manages our communication with the container.
        private IOleClientSite _clientSite;

        // I tried to use the OLE advise holder in dataAdviseThread, but it
        // hung mysteriously during shutdown.  Something was blocking the
        // thread.
        private IList<IAdviseSink> _advises = new List<IAdviseSink>();
        
        private DataAdviseThread _dataAdviseThread;

        Win32.tagSIZEL _extent = new Win32.tagSIZEL(10000, 5000);  // HIMETRIC

        // Can render box in various formats.  Implements most of IDataObject.
        private GraphRenderer _renderer;

        // If we are being edited then this is our (MDI child) form.
        private GraphForm _form;

        // Parent's moniker, if any.
        private IMoniker _fileMoniker = null;

        // If we are registered in the running-objects table.
        private int _rotCookie = 0;

        #region Properties

        public string Name {
            get { return _name; }
            set {
                _name = value;
                AdviseDataChanged();
            }
        }

        public string GraphType {
            get { return _graphType; }
            set { 
                _graphType = value;
                AdviseDataChanged();
            }
        }

        public IList<IAdviseSink> Advises {
            get { return _advises; }
        }


        internal DataAdviseThread AdviseThread {
            get { return _dataAdviseThread; }
            private set { _dataAdviseThread = value; }
        }

        public IOleClientSite ClientSite {
            get { return _clientSite; }
            private set { _clientSite = value; }
        }

        public GraphForm Form { 
            get { return _form; }
            private set { _form = value; }
        }

        public GraphRenderer Renderer { 
            get { return _renderer; }
            private set { _renderer = value; }
        }

        public Win32.tagSIZEL Extent {
            get { return _extent; }
            private set {
                _extent = value;
                NeedsSave();
                AdviseDataChanged();
                NeedsSave();
            }
        }

        public int Cookie {
            get { return _rotCookie; }
        }

        #endregion

        /// <summary>
        /// Constructor used by COM.
        /// </summary>
        public GraphBox()
        {
            using (new IndentLog("Constructing Box for COM")) {
                CommonInit(true, null);
            }
        }

        /// <summary>Constructor for a non-embedded Box.  This is where we can
        /// start copying and pasting from.</summary>
        public GraphBox(string name, IMoniker file_moniker)
        {
            SetDefaults(name);
            CommonInit(true, file_moniker);
        }

        public GraphBox(XmlNode n, IMoniker file_moniker)
        {
            LoadFromXml(n);
            CommonInit(true, file_moniker);
        }

        /// <summary>Copy an object for placing on the clipboard.</summary>
        public GraphBox(GraphBox to_copy)
        {
            Name = to_copy.Name;
            //comment = to_copy.comment;
            Extent = new Win32.tagSIZEL(to_copy.Extent);
            RememberLoadedValues();
            CommonInit(false, to_copy.Moniker);  // No form: it will not change
        }

        void CommonInit(bool has_form, IMoniker file_moniker)
        {
            Moniker = file_moniker;
            Program.BoxCreated();
            Renderer = new GraphRenderer(this);
            AdviseThread = new DataAdviseThread();
            if (has_form)
                _form = new GraphForm(this);
            Log.Add("constructed");
        }

        void SetDefaults(string name)
        {
            Extent = new Win32.tagSIZEL(10000, 10000);
            this.Name = name;
            //comment = "No comment";
            RememberLoadedValues();
        }

        void LoadFromXml(XmlNode n)
        {
            Name = n.Attributes["name"].Value;
            GraphType = n.Attributes["type"].Value;

            Extent.cx = XmlConvert.ToInt32(n.Attributes["extentcx"].Value);
            Extent.cy = XmlConvert.ToInt32(n.Attributes["extentcy"].Value);
            //comment = n.InnerText;
            //Log.Add(String.Format("Loaded {0} ({1}x{2}) - {3}", name, extent.cx, extent.cy, comment));
            RememberLoadedValues();
        }

        internal void SaveToXml(XmlWriter w)
        {
            w.WriteStartElement("Box");
            w.WriteAttributeString("name", Name);
            w.WriteAttributeString("type", GraphType);
            w.WriteAttributeString("extentcx", XmlConvert.ToString(Extent.cx));
            w.WriteAttributeString("extentcy", XmlConvert.ToString(Extent.cy));
            //w.WriteString(comment);
            w.WriteEndElement();
        }

        void RememberLoadedValues()
        {
            //loadedExtent = new Win32.tagSIZEL(extent);
            //loadedName = name;
            //loadedComment = comment;
        }

        ~GraphBox()
        {
            Log.Add("destroyed");
            if (AdviseThread != null)
                AdviseThread.Stop();
            Program.BoxDestroyed();
        }

        bool NeedsSave()
        {
            return true;
        }

        

        public void CloseBox()
        {
            // Tell the container to save our changes.
            using (new IndentLog("CloseBox")) {
                Log.Add("closing 1");
                ClientSiteSaveObject();
                Log.Add("closing 2");
                ClientSiteOnShowWindow(false);
                Log.Add("closing 3");
                AdviseOnClose();
                Log.Add("closing 4");
                //BoxesDoc.ROTUnregister(ref rotCookie);

                // Disconnect the container.
                Log.Add("Disconnecting object");
                Win32.CoDisconnectObject(this, 0);
            }
        }

        internal void FileRenamed(IMoniker file_moniker)
        {
            Moniker = file_moniker;

            if (Cookie != 0)
                ROTRegisterAsRunning();

            AdviseOnRename();
        }

        public IMoniker Moniker
        {
            get
            {
                IMoniker item_moniker, new_moniker;
                int hr = Win32.CreateItemMoniker("!", Name, out item_moniker);
                Debug.Assert(hr == Win32.NOERROR);
                hr = Win32.CreateGenericComposite(_fileMoniker, item_moniker, out new_moniker);
                Debug.Assert(hr == Win32.NOERROR);
                return new_moniker;
            }
            private set {
                _fileMoniker = value;
            }
        }

        public void ROTRegisterAsRunning()
        {
            // This will unregister and reregister redundantly in some cases.
            //BoxesDoc.ROTRegisterAsRunning(Moniker, this, ref rotCookie, typeof(IOleObject));
        }

        public int MiscStatus(int aspect)
        {
            int misc = Win32.OLEMISC_CANTLINKINSIDE;
            if (aspect == (int)DVASPECT.DVASPECT_CONTENT)
                misc |= Win32.OLEMISC_RECOMPOSEONRESIZE;
            return misc;
        }

        #region Advisements

        void AdviseOnSave()
        {
            foreach (IAdviseSink sink in Advises) {
                if (sink != null) {
                    using (new IndentLog("Calling IAdviseSink.OnSave()")) {
                        sink.OnSave();
                    }
                }
            }
        }

        public void AdviseOnClose()
        {
            foreach (IAdviseSink sink in new List<IAdviseSink>(Advises)) {
                if (sink != null) {
                    using (new IndentLog("Calling IAdviseSink.OnClose()")) {
                        try {
                            // I have observed that Word does not call Unadvise
                            // when it closes, so we get various errors out of
                            // this.
                            sink.OnClose();
                        } catch (Exception e) {
                            Log.Add("sink.OnClose() failed: " + e.Message);
                        }
                    }
                }
            }
        }

        public void AdviseOnRename()
        {
            foreach (IAdviseSink sink in new List<IAdviseSink>(Advises)) {
                if (sink != null) {
                    using (new IndentLog("Calling IAdviseSink.OnRename()")) {
                        sink.OnRename(Moniker);
                    }
                }
            }
        }

        public void ClientSiteSaveObject()
        {
            if (NeedsSave() && ClientSite != null) {
                using (new IndentLog("Calling IClientSite.SaveObject()")) {
                    ClientSite.SaveObject();
                }
            }
        }

        public void AdviseDataChanged()
        {
            if (AdviseThread != null) {
                AdviseThread.Invoke("SendOnDataChanging", delegate() {
                    AdviseThread.Holder.SendOnDataChange(this, 0, 0);
                });
            }

            if (Cookie != 0) {
                System.Runtime.InteropServices.ComTypes.FILETIME ft = new System.Runtime.InteropServices.ComTypes.FILETIME();
                Win32.CoFileTimeNow(out ft);
                Win32.GetROT().NoteChangeTime(Cookie, ref ft);
            }
        }

        public void ClientSiteOnShowWindow(bool shown)
        {
            if (ClientSite != null) {
                using (new IndentLog("Calling IClientSite.OnShowWindow({0})", shown)) {
                    ClientSite.OnShowWindow(shown);
                }
            }
        }

        #endregion

        #region COM IDataObject Members

        public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
        {
            Log.Add("IDataObject.GetCanonicalFormatEtc {0}", Log.FormatEtcToString(formatIn));
            formatOut = formatIn;
            return Win32.DV_E_FORMATETC;
        }

        /// <summary>
        /// Sets data in the specified format into storage.
        /// </summary>
        /// <param name="formatIn">The format of the data.</param>
        /// <param name="medium">The data.</param>
        /// <param name="release">If true, ownership of the medium's memory will be transferred
        /// to this object. If false, a copy of the medium will be created and maintained, and
        /// the caller is responsible for the memory of the medium it provided.</param>
        public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
        {
            Log.Add("IDataObject.SetData - NOT SUPPORTED!");
            throw new NotSupportedException();
        }

        /// <summary>
        /// Gets an enumerator for the formats contained in this DataObject.
        /// </summary>
        /// <param name="direction">The direction of the data.</param>
        /// <returns>An instance of the IEnumFORMATETC interface.</returns>
        public IEnumFORMATETC EnumFormatEtc(DATADIR direction)
        {
            using (new IndentLog("IDataObject.EnumFormatEtc")) {
                try {
                    // We only support GET
                    if (DATADIR.DATADIR_GET == direction)
                        return Renderer.EnumFormatEtc();
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }

                Log.Add("NOT IMPLEMENTED!");
                throw new NotImplementedException("OLE_S_USEREG");
            }
        }

        /// <summary>
        /// Gets the specified data.
        /// </summary>
        /// <param name="format">The requested data format.</param>
        /// <param name="medium">When the function returns, contains the requested data.</param>
        public void GetData(ref FORMATETC format, out STGMEDIUM medium)
        {
            using (new IndentLog("IDataObject.GetData({0})", Log.FormatEtcToString(format))) {
                Renderer.GetData(ref format, out medium);
            }
        }

        public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
        {
            using (new IndentLog("IDataObject.GetDataHere({0})", Log.ClipboardFormatName(format.cfFormat))) {
                Renderer.GetDataHere(ref format, ref medium);
            }
        }

        /// <summary>
        /// Determines if data of the requested format is present.
        /// </summary>
        /// <param name="format">The request data format.</param>
        /// <returns>Returns the status of the request. If the data is present, S_OK is returned.
        /// If the data is not present, an error code with the best guess as to the reason is returned.</returns>
        public int QueryGetData(ref FORMATETC format)
        {
            using (new IndentLog("IDataObject.QueryGetData({0})", Log.ClipboardFormatName(format.cfFormat))) {
                try {
                    return Renderer.QueryGetData(ref format);
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection)
        {
            using (new IndentLog("IDataObject.DAdvise {0}, {1}", Log.FormatEtcToString(pFormatetc), advf)) {
                try {
                    if (pFormatetc.cfFormat != 0) {  // don't check wildcard
                        int res = Renderer.QueryGetData(ref pFormatetc);
                        if (res != Win32.S_OK) {
                            connection = 0;
                            return res;
                        }
                    }
                    FORMATETC etc = pFormatetc;
                    int conn = 0;
                    AdviseThread.Invoke("Advising", delegate() {
                        AdviseThread.Holder.Advise(this, ref etc, advf, adviseSink, out conn);
                    });
                    connection = conn;
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        public void DUnadvise(int connection)
        {
            using (new IndentLog("IDataObject.DUnadvise {0}", connection)) {
                try {
                    AdviseThread.Invoke("Unadvising", delegate() {
                        AdviseThread.Holder.Unadvise(connection);
                    });
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        public int EnumDAdvise(out IEnumSTATDATA enumAdvise)
        {
            Log.Add("IDataObject.EnumDAdvise -> OLE_E_ADVISENOTSUPPORTED");
            // Nobody implements this damn thing.
            enumAdvise = null;
            return Win32.OLE_E_ADVISENOTSUPPORTED;
        }

        #endregion

        #region COM IOleObject Members

        // Informs object of its client site in container.
        public int SetClientSite(IOleClientSite pClientSite)
        {
            Log.Add("IOleObject.SetClientSite");
            ClientSite = pClientSite;
            return Win32.NOERROR;
        }

        // Retrieves object's client site.
        public IOleClientSite GetClientSite()
        {
            Log.Add("IOleObject.GetClientSite");
            Debug.Assert(ClientSite != null);
            return ClientSite;
        }

        // Communicates names of container application and container document.
        // Only called when we are embedded.
        public int SetHostNames(string szContainerApp, string szContainerObj)
        {
            using (new IndentLog("IOleObject.SetHostNames({0}, {1})", szContainerApp, szContainerObj)) {
                try {
                    if (Form != null)
                        Program.mainThread.BeginInvoke(delegate() {
                            Program.SetAsEmbedded(szContainerApp, szContainerObj);
                            Log.SetTitle(szContainerObj + " in " + szContainerApp);
                        });
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        // Moves object from running to loaded state.
        public int Close(int dwSaveOption)
        {
            using (new IndentLog("IOleObject.Close({0})", dwSaveOption)) {
                try {
                    bool save = false, cancelled = false;

                    if (dwSaveOption == (int)Win32.tagOLECLOSE.OLECLOSE_SAVEIFDIRTY && NeedsSave())
                        save = true;

                    if (dwSaveOption == (int)Win32.tagOLECLOSE.OLECLOSE_PROMPTSAVE && NeedsSave()) {
                        Program.mainThread.Invoke(delegate() {
                            // If asked to prompt, do so only if dirty: if we get a YES, save as
                            // usual and close.  On NO, just close.  On CANCEL, return
                            // OLE_E_PROMPTSAVECANCELLED.
                            System.Windows.Forms.DialogResult r;
                            r = System.Windows.Forms.MessageBox.Show("Save?", "Box", System.Windows.Forms.MessageBoxButtons.YesNoCancel);
                            switch (r) {
                                case System.Windows.Forms.DialogResult.Yes:
                                    save = true;
                                    break;
                                case System.Windows.Forms.DialogResult.Cancel:
                                    cancelled = true;
                                    break;
                            }
                        });
                    }

                    if (cancelled) {
                        Log.Add("-> OLE_E_PROMPTSAVECANCELLED");
                        return Win32.OLE_E_PROMPTSAVECANCELLED;
                    }

                    if (save) {
                        ClientSiteSaveObject();
                        AdviseOnSave();
                    }

                    if (Form != null) {
                        Program.mainThread.Invoke(delegate() {
                            // Avoids form OnClose handler.
                            Form.Dispose();
                        });
                        Form = null;
                    }
                    // Regardless of whether the form has been shown we must
                    // do all the normal shutdown actions.  (e.g. WinWord 2007)
                    CloseBox();
                    Log.Add("-> NOERROR");
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        // Informs object of its moniker.
        public int SetMoniker(int dwWhichMoniker, object pmk)
        {
            Log.Add("IOleObject.SetMoniker -> NotImplementedException");
            throw new NotImplementedException();
        }

        // Retrieves object's moniker.
        public int GetMoniker(int dwAssign, int dwWhichMoniker, out object moniker)
        {
            Log.Add("IOleObject.GetMoniker -> NotImplementedException");
            throw new NotImplementedException();
        }

        // Initializes embedded object from selected data.
        public int InitFromData(IDataObject pDataObject, int fCreation, int dwReserved)
        {
            Log.Add("IOleObject.InitFromData -> NotImplementedException");
            // See p900 if we need this.
            throw new NotImplementedException();
        }

        // Retrieves a data transfer object from (for?) the clipboard.
        public int GetClipboardData(int dwReserved, out IDataObject data)
        {
            using (new IndentLog("IOleObject.GetClipboardData")) {
                try {
                    GraphBox new_box = new GraphBox(this);
                    data = new_box;
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        // Invokes object to perform one of its enumerated actions.
        public int DoVerb(int iVerb, IntPtr lpmsg, IOleClientSite pActiveSite, int lindex, IntPtr hwndParent, Win32.COMRECT lprcPosRect)
        {
            using (new IndentLog("IOleObject.DoVerb {0}", iVerb)) {
                try {
                    // I saw OLEIVERB_HIDE come in as 253.  Perhaps a unsigned
                    // byte conversion happening somewhere.
                    if (iVerb >= 250 && iVerb <= 255) {
                        int new_iverb = iVerb - 256;
                        Log.Add("Fixing iVerb: {0} -> {1}", iVerb, new_iverb);
                        iVerb = new_iverb;
                    }

                    switch (iVerb) {
                        case Win32.OLEIVERB_PRIMARY:
                        case Win32.OLEIVERB_OPEN:
                        case Win32.OLEIVERB_SHOW:
                        case Win32.OLEIVERB_UPDATE:
                            if (iVerb == Win32.OLEIVERB_PRIMARY)
                                Log.Add("OLEIVERB_PRIMARY");
                            else if (iVerb == Win32.OLEIVERB_OPEN)
                                Log.Add("OLEIVERB_OPEN");
                            else if (iVerb == Win32.OLEIVERB_UPDATE) {
                                Log.Add("OLEIVERB_UPDATE");
                                AdviseDataChanged();
                                return Win32.S_OK;
                            } else {
                                Log.Add("OLEIVERB_SHOW");
                            }

                            Program.mainThread.Invoke(delegate() {
                                //Program.mainForm.Show();
                                //Win32.SetForegroundWindow(Program.mainForm.Handle);
                                Form.Show();
                                Form.Activate();
                            });
                            if (ClientSite != null) {
                                using (new IndentLog("ClientSite.ShowObject()")) {
                                    ClientSite.ShowObject();
                                }
                                ClientSiteOnShowWindow(true);
                            }
                            break;
                        case Win32.OLEIVERB_HIDE:
                            Log.Add("OLEIVERB_HIDE");
                            Program.mainThread.Invoke(delegate() {
                                Form.Hide();
                            });
                            ClientSiteOnShowWindow(false);
                            break;
                        default:
                            Debug.Assert(false, "Unexpected verb: "+iVerb.ToString());
                            return Win32.OLEOBJ_S_INVALIDVERB;
                    }
                    Log.Add("-> NOERROR");
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        // Enumerates actions ("verbs") for an object.
        public int EnumVerbs(out IEnumOLEVERB e)
        {
            Log.Add("IOleObject.EnumVerbs -> OLE_S_USEREG");
            e = null;
            return Win32.OLE_S_USEREG;
        }

        // Updates an object.
        public int OleUpdate()
        {
            Log.Add("IOleObject.OleUpdate -> NOERROR");
            // We are always updated since we don't contain.
            return Win32.NOERROR;
        }

        // Checks if object is up to date.
        public int IsUpToDate()
        {
            Log.Add("IOleObject.IsUpToDate -> NOERROR");
            // We are always updated since we don't contain.
            return Win32.NOERROR;
        }

        // Returns an object's class identifier.
        public int GetUserClassID(ref Guid pClsid)
        {
            Log.Add("IOleObject.GetUserClassID");
            if (Program.isEmbedded)
                pClsid = CLSID;
            //else
            //    pClsid = BoxesDoc.ClsID;
            return Win32.NOERROR;
        }

        // Retrieves object's user-type name.
        public int GetUserType(int dwFormOfType, out string userType)
        {
            Log.Add("IOleObject.GetUserType -> OLE_S_USEREG");
            // I suppose it is worthwhile to include this information in the
            // registry because it allows handlers to know our type without
            // creating an instance of this class.  That being the case we
            // just refer callers to the registry.
            userType = USER_TYPE;
            return Win32.OLE_S_USEREG;
        }

        // Sets extent of object's display area.
        public int SetExtent(int dwDrawAspect, Win32.tagSIZEL pSizel)
        {
            using (new IndentLog("IOleObject.SetExtent({0}x{1})", pSizel.cx, pSizel.cy)) {
                try {
                    if ((dwDrawAspect & (int)DVASPECT.DVASPECT_CONTENT) == 0)
                        return Win32.E_FAIL;
                    Extent = new Win32.tagSIZEL(pSizel.cx, pSizel.cy);
                    if (Form != null) {
                        Program.mainThread.Invoke(delegate() {
                            Form.BoxUpdated();
                        });
                    }
                    // Changes to size should be preserved.
                    ClientSiteSaveObject();
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        // Retrieves extent of object's display area.
        public int GetExtent(int dwDrawAspect, Win32.tagSIZEL pSizel)
        {
            Log.Add("IOleObject.GetExtent");
            if ((dwDrawAspect & (int)DVASPECT.DVASPECT_CONTENT) == 0) {
                Log.Add("-> E_FAIL");
                return Win32.E_FAIL;
            }
            pSizel.cx = Extent.cx;
            pSizel.cy = Extent.cy;

            return Win32.NOERROR;
        }

        // Establishes advisory connection with object.
        public int Advise(IAdviseSink pAdvSink, out int cookie)
        {
            using (new IndentLog("IOleObject.Advise")) {
                try {
                    // We would have liked to use CreateOleAdviseHolder, but it crashes
                    // when accessed from multiple threads, and we seem to get every
                    // call from a different thread!
                    cookie = Advises.Count;
                    Advises.Add(pAdvSink);
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        // Destroys advisory connection with object.
        public int Unadvise(int dwConnection)
        {
            using (new IndentLog("IOleObject.Unadvise")) {
                try {
                    //oleAdviseHolder.Unadvise(dwConnection);
                    Debug.Assert(Advises[dwConnection] != null);
                    Advises[dwConnection] = null;
                    return Win32.NOERROR;
                } catch (Exception e) {
                    Log.Add(e);
                    throw;
                }
            }
        }

        // Enumerates object's advisory connections.
        public int EnumAdvise(out IEnumSTATDATA e)
        {
            Log.Add("IOleObject.EnumAdvise -> E_NOTIMPL");
            e = null;
            return Win32.E_NOTIMPL;
        }

        // Retrieves status of object.
        public int GetMiscStatus(int dwAspect, out int misc)
        {
            using (new IndentLog("IOleObject.GetMiscStatus({0})", dwAspect)) {
                misc = MiscStatus(dwAspect);
                Log.Add("misc -> {0}", misc);
                return Win32.NOERROR;
            }
        }

        /// <summary>Recommends color scheme to object application.  See
        /// Brockschmidt p901.</summary>
        public int SetColorScheme(Win32.tagLOGPALETTE pLogpal)
        {
            Log.Add("IOleObject.SetColorScheme -> NotImplementedException");
            throw new NotImplementedException();
        }

        #endregion

        #region COM IPersistStorage

        // We do not hang onto our IStorage as advocated in Brockschmidt chapter
        // 8 because I do not care about low memory conditions for this example.

        /// <summary>So people who save us can find the right class to reload
        /// with.</summary>
        public void GetClassID(out Guid pClassID)
        {
            Log.Add("IPersistStorage.GetClassID");
            pClassID = CLSID;
        }
        
        /// <summary>Whether we require saving.</summary>
        public int IsDirty()
        {
            using (new IndentLog("IPersistStorage.IsDirty")) {
                // This should always return false when we are open for editing
                // because the container should have been notified about any
                // changes (Brockschmidt p883)
                return NeedsSave() ? Win32.S_OK : Win32.S_FALSE;
            }
        }

        public void InitNew(IStorage pstg)
        {
            using (new IndentLog("IPersistStorage.InitNew")) {
                // We don't need an IStorage except at Load/Save.
                SetDefaults("New");
            }
        }

        public int Load(IStorage pstg)
        {
            using (new IndentLog("IPersistStorage.Load")) {
                // Read our XML data.
                byte[] xml_bytes;
                // These calls to ReleaseComObject may not be necessary, but who
                // can really be sure?
                try {
                    IStream istrm = pstg.OpenStream("BoxXml", IntPtr.Zero, (int)(Win32.STGM.READ | Win32.STGM.SHARE_EXCLUSIVE), 0);
                    try {
                        System.Runtime.InteropServices.ComTypes.STATSTG stat = new System.Runtime.InteropServices.ComTypes.STATSTG();
                        istrm.Stat(out stat, 1 /*STATFLAG_NONAME*/);
                        xml_bytes = new byte[stat.cbSize];
                        istrm.Read(xml_bytes, xml_bytes.Length, IntPtr.Zero);
                    } finally {
                        Marshal.ReleaseComObject(istrm);
                    }
                } finally {
                    Marshal.ReleaseComObject(pstg);
                }

                XmlDocument doc = new XmlDocument();
                doc.Load(new MemoryStream(xml_bytes));
                LoadFromXml(doc.SelectSingleNode("Box"));
                Form.BoxUpdated();
                return Win32.S_OK;
            }
        }

        public void Save(IStorage pStgSave, bool fSameAsLoad)
        {
            using (new IndentLog("IPersistStorage.Save")) {
                // Convert our data to XML.
                MemoryStream ms = new MemoryStream();
                using (XmlWriter w = XmlWriter.Create(ms)) {  // defaults to UTF8
                    SaveToXml(w);
                }

                // Store this in a stream.
                try {
                    Win32.WriteClassStg(pStgSave, CLSID);
                    Win32.WriteFmtUserTypeStg(pStgSave, (uint)Win32.RegisterClipboardFormat("Box"), GraphBox.USER_TYPE);
                    IStream istrm = pStgSave.CreateStream("BoxXml",
                                                          (int)(Win32.STGM.DIRECT | Win32.STGM.READWRITE | Win32.STGM.CREATE | Win32.STGM.SHARE_EXCLUSIVE), 0, 0);
                    try {
                        istrm.Write(ms.GetBuffer(), (int)ms.Length, IntPtr.Zero);
                    } finally {
                        Marshal.ReleaseComObject(istrm);
                    }
                } finally {
                    Marshal.ReleaseComObject(pStgSave);
                }
                RememberLoadedValues();
            }
        }

        public void SaveCompleted(IStorage pStgNew)
        {
            using (new IndentLog("IPersistStorage.SaveCompleted")) {
                // We only ever write in response to Save().
                AdviseOnSave();
            }
        }

        public void HandsOffStorage()
        {
            Log.Add("IPersistStorage.HandsOffStorage");
            // Don't worry, we won't do anything unless asked.
        }

        #endregion
    }
}

